//********************************************************************
//	created:	20:10:2011   11:55
//	filename: 	mysqlstatement.cpp
//	author:		tiamo
//	purpose:	mysql statement
//********************************************************************

#include "stdafx.h"
#include "mysqlstatement.h"

namespace mysql
{
	//
	// constructor
	//
	Statement::Statement(MYSQL_STMT* statement, Database& database)
		: mDatabase(database), mStatement(statement)
	{

	}

	//
	// close
	//
	void Statement::close()
	{
		if(mStatement)
			mysql_stmt_close(mStatement);

		mStatement															= nullptr;
		mParamBindBuffer.resize(0);
		mResultBindBuffer.resize(0);
		mTempStringList.clear();
	}

	//
	// reset
	//
	void Statement::reset(bool resetStatement, bool resetBind)
	{
		if(resetStatement && mStatement && mysql_stmt_reset(mStatement))
			mDatabase.sqlError(true, mStatement);

		if(resetBind)
		{
			mParamBindBuffer.resize(0); 
			mResultBindBuffer.resize(0); 
			mTempStringList.clear();
		}
	}

	//
	// prepare
	//
	void Statement::prepareV(wchar_t const* statementFormat, ...)
	{
		va_list argList;
		va_start(argList, statementFormat);
		wchar_t statementString[4096]										= {0};
		platform::vsnwprintf(statementString, ARRAYSIZE(statementString), statementFormat, argList);
		va_end(argList);

		prepare(statementString);
	}

	//
	// prepare
	//
	void Statement::prepare(wchar_t const* statementString)
	{
		if(!mStatement)
			ThrowException(L"mysql::Statement(%p): invalid mysql statement handle", this);

		reset(true, true);

		Utf8String query													= utils::wideToUtf8(statementString);
		if(mysql_stmt_prepare(mStatement, query.c_str(), static_cast<uint32_t>(query.length())))
			mDatabase.sqlError(true, mStatement);
	}

	//
	// bind string
	//
	void Statement::bind(wchar_t const* fieldName, bool bindParam, UniString& theValue, uint32_t reserveLength, bool* isNull)
	{
		size_t theKey														= reinterpret_cast<size_t>(&theValue);
		if(mTempStringList.find(theKey) != mTempStringList.end())
			ThrowException(L"mysql::Statement(%p): %ls already bind", fieldName);

		std::pair<utils::DataBuffer, unsigned long>& tempString				= mTempStringList[theKey];
		if(bindParam)
		{
			Utf8String temp													= utils::wideToUtf8(theValue);
			tempString.second												= static_cast<uint32_t>(temp.length());
			tempString.first.appendData(temp.c_str(), static_cast<uint32_t>(temp.length()));
		}
		else
		{
			reserveLength													*= 3;
			tempString.first.reserve(reserveLength);
			tempString.second												= reserveLength;
		}

		bind(fieldName, bindParam, MYSQL_TYPE_STRING, tempString.first.getBuffer(), tempString.first.getSize(), false, &tempString.second, reinterpret_cast<my_bool*>(isNull));
	}

	//
	// bind
	//
	void Statement::bind(wchar_t const* fieldName, bool bindParam, enum_field_types bufferType, void* valueBuffer, uint32_t bufferLength, my_bool isUnsigned, unsigned long* valueLength, my_bool* isNull)
	{
		MYSQL_BIND bindItem													= {0};
		bindItem.buffer														= valueBuffer;
		bindItem.buffer_length												= bufferLength;
		bindItem.buffer_type												= bufferType;
		bindItem.is_null													= isNull;
		bindItem.length														= valueLength;
		bindItem.is_unsigned												= isUnsigned;

		if(bindParam)
			mParamBindBuffer.appendData(&bindItem, sizeof(bindItem));
		else
			mResultBindBuffer.appendData(&bindItem, sizeof(bindItem));
	}

	//
	// execute
	//
	void Statement::execute()
	{
		if(!mStatement)
			ThrowException(L"mysql::Statement(%p): invalid mysql statement handle", this);

		//
		// check param count
		//
		uint32_t needCount													= static_cast<uint32_t>(mysql_stmt_param_count(mStatement));
		if(needCount != mParamBindBuffer.getLength() / sizeof(MYSQL_BIND))
			ThrowException(L"mysql::Statement(%p): invalid bind param (%u != %u)", this, needCount, static_cast<uint32_t>(mParamBindBuffer.getLength() / sizeof(MYSQL_BIND)));

		
		//
		// check result count
		//
		MYSQL_RES* metaResult												= mysql_stmt_result_metadata(mStatement);
		ON_BLOCK_EXIT([&metaResult]{if(metaResult)mysql_free_result(metaResult);});
		needCount															= metaResult ? static_cast<uint32_t>(mysql_num_fields(metaResult)) : 0;
		if(needCount != mResultBindBuffer.getLength() / sizeof(MYSQL_BIND))
			ThrowException(L"mysql::Statement(%p): invalid bind param (%u != %u)", this, needCount, static_cast<uint32_t>(mResultBindBuffer.getLength() / sizeof(MYSQL_BIND)));

		//
		// bind param
		//
		if(mParamBindBuffer.getLength() && mysql_stmt_bind_param(mStatement, static_cast<MYSQL_BIND*>(mParamBindBuffer.getBuffer())))
			mDatabase.sqlError(true, mStatement);

		//
		// execute it
		//
		if(mysql_stmt_execute(mStatement))
			mDatabase.sqlError(true, mStatement);

		//
		// bind result
		//
		if(mResultBindBuffer.getLength() && mysql_stmt_bind_result(mStatement, static_cast<MYSQL_BIND*>(mResultBindBuffer.getBuffer())))
			mDatabase.sqlError(true, mStatement);
	}

	//
	// fetch
	//
	bool Statement::fetch(bool failIfTruncated)
	{
		if(!mStatement)
			ThrowException(L"mysql::Statement(%p): invalid mysql statement handle", this);

		//
		// fetch
		//
		int retValue														= mysql_stmt_fetch(mStatement);

		//
		// error condition
		//
		if(retValue == 1)
			mDatabase.sqlError(true, mStatement);

		//
		// no more item
		//
		if(retValue == MYSQL_NO_DATA)
			return false;

		//
		// truncated
		//
		if(retValue == MYSQL_DATA_TRUNCATED && failIfTruncated)
			ThrowException(L"mysql::Statement(%p): data truncated", this);

		//
		// convert string
		//
		for(auto it = mTempStringList.begin(); it != mTempStringList.end(); ++ it)
			*reinterpret_cast<UniString*>(it->first)						= utils::utf8ToWide(static_cast<char const*>(it->second.first.getBuffer()), it->second.second);

		return true;
	}
}
